home *** CD-ROM | disk | FTP | other *** search
/ The Fatted Calf / The Fatted Calf.iso / Modules / BackSpaceModules / Source / SparseLifeView / SparseLifeView.m < prev    next >
Text File  |  1993-11-04  |  22KB  |  718 lines

  1. // SparseLifeView by David Bau
  2. //
  3. // I feel silly even saying this, but please don't charge for this
  4. // module or any modification of it, and please give credit where
  5. // credit is due.
  6. // 
  7. // This code is shamelessly ripped off of Sam Streeper's Life module.
  8. // Here is Sam's original description.
  9. //
  10. // ****
  11. // Life is the classical demonstration of cellular automata.
  12. // It was originally created as a simplisting simulation of the dynamics
  13. // of living communities.  I've always thought these things are pretty
  14. // cool; though the algorithm behind Life is exceedingly simple,
  15. // getting good performance seems to require different hacks for
  16. // the display architecture of every machine.
  17. // ...
  18. // Living cell with < 2 neighbors    -> dies of isolation
  19. // Living cell with 2 or 3 neighbors -> lives
  20. // Living cell with > 3 neighbors    -> dies of overcrowding
  21. // empty cell with 3 neighbors       -> life is created (reproduction)
  22. // ...
  23. // ****
  24. // 
  25. // I've changed several things.  First, the method for computing and
  26. // drawing cells was changed to a sparse algorithm that traverses a
  27. // list of live cells.  This way empty cells are looked at as little
  28. // as possible, which is a big win when the field is mostly empty.
  29. // As a nice side effect, the squares get drawn in a (more or less)
  30. // uniformly random order, which gives a smoother visual effect
  31. // without any left-to-right flickering.
  32. //
  33. // The drawing-buffering code was cleaned up.  Smaller lists of rects
  34. // are used, but one for each color.  No noticable performance hit.
  35. //
  36. // A hack in drawSelf was added to work around the double-refresh
  37. // problem when the screensaver first kicks in. (countDown!=ITERATIONS)
  38. //
  39. // The initLife seeding algorithm was changed.  Now it puts inital
  40. // cells in a small circular patch, leaving the rest of the field
  41. // empty, which is much better for the sparse algorithm.  For big
  42. // windows, the circular patch appears in a random starting place.
  43. //
  44. // Stasis checking was changed.  Now it automatically deduces any
  45. // stasis period (up to the size of the stasis buffer), but it takes
  46. // more generations for stasis to be detected.
  47. //
  48. // The color table was generalized to dish out NXColors, instead of
  49. // just hues.  Cell size was generalized.
  50. //
  51. // The control panel was souped up.  Now the color table can be
  52. // changed by the user.  The cell sizes can be changed.  The panel
  53. // animation appears (slowly) partially obscured by the panel
  54. // controls.  The panel animation can be stepped manually by the
  55. // user. Defaults are saved in the user's defaults database.
  56. // Programming the panel was the hardest part, but the result
  57. // looks pretty nice.  I'm beginning to appreciate IB.
  58. //
  59. // 10/20/93 fixes and improvements: used perform:with:afterDelay to
  60. // credits button remove itself correctly.  Also used the same method
  61. // to do the panel animation in a more polite, lazy way.  Changed
  62. // "step" button to a continuous button.  Fixed the timed panel
  63. // animation so it doesn't draw if the view is gone from the window.
  64. //
  65. // 10/23/93 Changed sizing buttons to a matrix of buttons.  Stop
  66. // timed panel animation if already doing an animation somewhere else
  67. // (window or background) by making sure countDown isn't changing.
  68. //
  69. // 10/27/93 Added LIVEEDGE boundary convention so unseen boundary has
  70. // "nutrients" instead of permanent empty squares.  The result is that
  71. // drifters blow up instead of turning into blocks at the edge.
  72. //
  73. // The rules with LIVEEDGE enabled are:
  74. //    Cell dies if it has <2 or >3 neighbors, counting edge neighbors
  75. //    Cell is born if it has =3 neighbors, not counting edge neighbors
  76. //
  77. // Made it so drawing can go right up to the edge of the window,
  78. // using a random offset when it can't completely fill it. Removed
  79. // unneeded calls to flushColor to avoid NXSetColor calls.
  80. //
  81. // 10/29/93 Added DEEPEDGE, so field can extend beyond the visible screen.
  82. // turned off LIVEEDGE feature.  Fine tuned seeding parameters.
  83. //
  84. // 11/05/93 Changed MINCELLSIZE to 2, which can be obtained by a dwrite
  85. // to SparseLifeCellSize only.  Played with matrix of radio buttons for
  86. // so none are highlighed when cellSize matches none of them (e.g.=2),
  87. // but so that once the user starts selecting them, they go into
  88. // setEmptySelectionEnabled:NO mode.
  89. //
  90. // If you add any neat stuff to this module or derive anything
  91. // interesting from it, I'd be interested to see!
  92. //
  93. // I'll probably be bau@cs.cornell.edu for a while.
  94. //
  95. // David Bau 11/05/93
  96. // 777 South Avenue, Weston, MA 02193
  97.  
  98.  
  99.  
  100. #import <appkit/appkit.h>
  101. #import <defaults/defaults.h>
  102. #import <libc.h>
  103. #import <time.h>
  104. #import <dpsclient/wraps.h>
  105. #import "SparseLifeView.h"
  106. #import "Thinker.h"
  107.  
  108.  
  109. /* the kind of Life view that appears in the control panel */
  110. @implementation StaticSparseLifeView
  111.  
  112. - initLife
  113. {
  114.     int x,y,count;
  115.     /* the field is bigger than the view */
  116.     ncols=MIN((bounds.size.width/cellSize+2+2*DEEPEDGE+10),MAXCOLS);
  117.     nrows=MIN((bounds.size.height/cellSize+2+2*DEEPEDGE+10),MAXROWS);
  118.     xoffset=(bounds.size.width-ncols*cellSize)/2;
  119.     yoffset=(bounds.size.height-nrows*cellSize)/2;
  120.  
  121.     ifirst = -1;
  122.     for (x=0; x<ncols; x++) {
  123.     for (y=0; y<nrows; y++) {
  124.             if (x==0 || y==0 || x==ncols-1 || y==nrows-1) {
  125.                 /* Grid[x+y*MAXCOLS].neighbors=0; will contain nonsense */
  126.                 Grid[x+y*MAXCOLS].color=(-2);
  127.             } else {
  128.                 Grid[x+y*MAXCOLS].neighbors=0;
  129.                 Grid[x+y*MAXCOLS].color=(-1);
  130.             }
  131.     }
  132.     }
  133.     /* use uniform random seeding */
  134.     for (count=ncols*nrows/5; count; count--) {
  135.         x=random()%(ncols-2)+1;
  136.         y=random()%(nrows-2)+1;
  137.         if (Grid[x+y*MAXCOLS].color==(-1)) {
  138.             Grid[x+y*MAXCOLS].color=0;
  139.             Grid[x+y*MAXCOLS].next=ifirst;
  140.             ifirst=x+y*MAXCOLS;
  141.         }
  142.         if (Grid[x+y*MAXCOLS].color<COLORS-1) Grid[x+y*MAXCOLS].color++;
  143.     }
  144.  
  145.     /* init stasis array */
  146.     for (x=0; x<STATSIZE; x++) stasis[x] = x;
  147.     sindex = 0;
  148.     spass = 0;
  149.  
  150.     /* don't go for too many generations.  Seems it would be boring. */
  151.     countDown = 1000;
  152.  
  153.     return self;
  154. }
  155.  
  156. /* main view can tell panel view what colors to use */
  157. - setYoungColor:(NXColor)yc MediumColor:(NXColor)mc OldColor:(NXColor)oc
  158. {
  159.     youngColor=yc;
  160.     mediumColor=mc;
  161.     oldColor=oc;
  162.     [self computeColors];
  163.     return self;
  164. }
  165.  
  166. /* main view can tell panel what cell size to use */
  167. - setLifeCellSize:(int)cs;
  168. {
  169.     cellSize=cs;
  170.     [self setupSquareBuffer];
  171.     return self;
  172. }
  173.  
  174. @end
  175.  
  176.  
  177.  
  178.  
  179.  
  180.  
  181. @implementation SparseLifeView
  182.  
  183. /* here's where the sparse computation and drawing is done */
  184. - oneStep
  185. {
  186.     int icur, *picur;
  187.     int checksum = 0;
  188.  
  189.     /* finished */
  190.     if (--countDown < 0)
  191.     {
  192.         [self initLife];
  193.         [self display];
  194.     }
  195.  
  196.     /* pass one: count up neighbors.  Hope gcc does cse well... */
  197. #ifdef LIVEEDGE
  198. #define CONTRIBUTE_TO_GRID(iadj)         \
  199.     if (Grid[iadj].color==(-1)) {        \
  200.         Grid[iadj].color=0;              \
  201.         Grid[iadj].next=ifirst;          \
  202.         ifirst=iadj;                     \
  203.     } else if (Grid[iadj].color==(-2))   \
  204.         Grid[icur].neighbors++;          \
  205.     Grid[iadj].neighbors++;
  206. #else
  207. #define CONTRIBUTE_TO_GRID(iadj)         \
  208.     if (Grid[iadj].color==(-1)) {        \
  209.         Grid[iadj].color=0;              \
  210.         Grid[iadj].next=ifirst;          \
  211.         ifirst=iadj;                     \
  212.     }                                    \
  213.     Grid[iadj].neighbors++;
  214. #endif
  215.     for (icur=ifirst; icur>=0; icur=Grid[icur].next) {
  216.         /* contribute to the west */
  217.         CONTRIBUTE_TO_GRID(icur-1);
  218.         CONTRIBUTE_TO_GRID(icur-1-MAXCOLS);
  219.         CONTRIBUTE_TO_GRID(icur-MAXCOLS);
  220.         CONTRIBUTE_TO_GRID(icur+1-MAXCOLS);
  221.         CONTRIBUTE_TO_GRID(icur+1);
  222.         CONTRIBUTE_TO_GRID(icur+1+MAXCOLS);
  223.         CONTRIBUTE_TO_GRID(icur+MAXCOLS);
  224.         CONTRIBUTE_TO_GRID(icur-1+MAXCOLS);
  225.     }
  226. #undef CONTRIBUTE_TO_GRID
  227.  
  228.     /* pass two: redraw and prune */
  229.     picur=&ifirst;
  230.     while (*picur>=0) {
  231.         /* a cell only if 2 neighbors and was a cell, or 3 neighbors */
  232.         if (((Grid[*picur].neighbors==2 && Grid[*picur].color) ||
  233.              Grid[*picur].neighbors==3)) {
  234.             if (Grid[*picur].color<COLORS-1) {
  235.                 Grid[*picur].color++;
  236.                 [self putSquare:(*picur)%MAXCOLS :(*picur)/MAXCOLS
  237.                       Color: Grid[*picur].color];
  238.             }
  239.             Grid[*picur].neighbors=0;
  240.             checksum+=Grid[*picur].color * *picur;
  241.             picur=&(Grid[*picur].next); /* advance */
  242.         } else {
  243.             if (Grid[*picur].color) {
  244.                 [self putSquare:(*picur)%MAXCOLS :(*picur)/MAXCOLS
  245.                           Color: 0];
  246.             }
  247.             Grid[*picur].color=(-1);
  248.             Grid[*picur].neighbors=0;
  249.             *picur=Grid[*picur].next; /* delete from list and advance */
  250.         }
  251.     }
  252.  
  253.     /* empty anything left in the drawing buffers */
  254.     [self flushSquares];
  255.     
  256.     /* check for termination if things are cycling */
  257.     [self checkStasis:checksum];
  258.  
  259.     return self;
  260. }
  261.  
  262.  
  263. - drawSquares
  264. {
  265.     int icur;
  266.  
  267.     for (icur=ifirst; icur>=0; icur=Grid[icur].next) {
  268.         [self putSquare:(icur%MAXCOLS):(icur/MAXCOLS)
  269.               Color:Grid[icur].color];
  270.     }
  271.     [self flushSquares];
  272.     return self;
  273. }
  274.  
  275. - putSquare:(int)x :(int)y Color:(int)color
  276. {
  277.     NXRect *newsquare;
  278. #if DEEPEDGE
  279.     if (y<(1+DEEPEDGE) || y>=nrows-(1+DEEPEDGE) ||
  280.         x<(1+DEEPEDGE) || x>=ncols-(1+DEEPEDGE)) return self;
  281. #endif
  282.     if (squareCount[color]>=SQUAREBLOCK) [self flushColor:color];
  283.     newsquare = squareBuffer[color]+squareCount[color];
  284.     newsquare->origin.x=x*cellSize+xoffset;
  285.     newsquare->origin.y=y*cellSize+yoffset;
  286.     squareCount[color]++;
  287.  
  288.     return self;
  289. }
  290.  
  291. - flushColor:(int)color
  292. {
  293.     if (squareCount[color]) {
  294.         NXSetColor(colorTable[color]);
  295.         NXRectFillList(squareBuffer[color],squareCount[color]);
  296.         squareCount[color]=0;
  297.     }
  298.     return self;
  299. }
  300.  
  301. - flushSquares
  302. {
  303.     int color;
  304.     for (color = 0; color<COLORS; color++) {
  305.         [self flushColor: color];
  306.     }
  307.     return self;
  308. }
  309.         
  310. - drawSelf:(const NXRect *)rects :(int)rectCount
  311. {
  312.     if (!rects || !rectCount) return self;
  313.     PSsetgray(0);
  314.     NXRectFill(rects);
  315.     if (countDown!=ITERATIONS) [self drawSquares];
  316.     return self;
  317. }
  318.  
  319. - (const char *) windowTitle
  320. {    return "Sparse Life";
  321. }
  322.  
  323. - setupSquareBuffer
  324. {
  325.     int i,b;
  326.     for (i=0; i<COLORS; i++) {
  327.         squareCount[i]=0;
  328.         for (b=0; b<SQUAREBLOCK; b++) {
  329.             squareBuffer[i][b].origin.x=0;
  330.             squareBuffer[i][b].origin.y=0;
  331.             squareBuffer[i][b].size.width=cellSize;
  332.             squareBuffer[i][b].size.height=cellSize;
  333.         }
  334.     }
  335.     return self;
  336. }    
  337.  
  338. - initFrame:(const NXRect *)frameRect
  339. {
  340.     [super initFrame:frameRect];
  341.     [self getLifeDefaults];
  342.         [self computeColors];
  343.         srandom(time(NULL));
  344.  
  345.         [self setupSquareBuffer];
  346.     [self initLife];
  347.  
  348.     return self;
  349. }
  350.  
  351. static void ColorToString(NXColor color, char *str)
  352. {
  353.     sprintf(str,"%f %f %f",NXRedComponent(color),
  354.             NXGreenComponent(color),NXBlueComponent(color));
  355. }
  356.  
  357. static NXColor ColorFromString(const char *str)
  358. {
  359.     NXColor color;
  360.     float r,g,b;
  361.     sscanf(str,"%f %f %f",&r,&g,&b);
  362.     r=(r>=0.0 && r<=1.0 ? r : 1.0);
  363.     g=(g>=0.0 && g<=1.0 ? g : 1.0);
  364.     b=(b>=0.0 && b<=1.0 ? b : 1.0);
  365.     color=NXConvertRGBToColor(r,g,b);
  366.     return color;
  367. }
  368.     
  369. - getLifeDefaults
  370. {
  371.     static NXDefaultsVector SparseLifeDefaults = {
  372.         {"SparseLifeYoungColor", "0.083331 0.000000 1.000000"},
  373.         {"SparseLifeMediumColor","0.916669 1.000000 0.000000"},
  374.         {"SparseLifeOldColor",   "0.400005 0.000000 0.066668"},
  375.         {"SparseLifeCellSize",   "8"},
  376.         {NULL}
  377.     };
  378.     
  379.     NXRegisterDefaults("BackSpace",SparseLifeDefaults);
  380.     youngColor=
  381.       ColorFromString(NXGetDefaultValue("BackSpace","SparseLifeYoungColor"));
  382.     mediumColor=
  383.       ColorFromString(NXGetDefaultValue("BackSpace","SparseLifeMediumColor"));
  384.     oldColor=
  385.       ColorFromString(NXGetDefaultValue("BackSpace","SparseLifeOldColor"));
  386.     sscanf(NXGetDefaultValue("BackSpace","SparseLifeCellSize"),"%d",&cellSize);
  387.     if (cellSize<MINCELLSIZE) cellSize=MINCELLSIZE;
  388.     if (cellSize>MAXCELLSIZE) cellSize=MAXCELLSIZE;
  389.  
  390.     return self;
  391. }
  392.  
  393.  
  394. - sizeTo:(NXCoord)width :(NXCoord)height
  395. {
  396.     [super sizeTo:width :height];
  397.     [self initLife];
  398.     return self;
  399. }
  400.  
  401. /* clear the field and do some random seeding */
  402. - initLife
  403. {
  404.     int x,y,count,i,xr,yr,xo,yo,xa,ya;
  405.  
  406.     ncols = MIN((bounds.size.width/cellSize)+2+2*DEEPEDGE,MAXCOLS);
  407.     nrows = MIN((bounds.size.height/cellSize)+2+2*DEEPEDGE,MAXROWS);
  408.     xoffset = random()%(1+(int)(bounds.size.width-
  409.                         (ncols-2-2*DEEPEDGE)*cellSize))-(1+DEEPEDGE)*cellSize;
  410.     yoffset = random()%(1+(int)(bounds.size.height-
  411.                         (nrows-2-2*DEEPEDGE)*cellSize))-(1+DEEPEDGE)*cellSize;
  412.  
  413.     /* clear the field */
  414.     ifirst = -1; /* empty linked list of live cells */
  415.     for (x=0; x<ncols; x++) {
  416.     for (y=0; y<nrows; y++) {
  417.             if (x==0 || y==0 || x==ncols-1 || y==nrows-1) {
  418.                 /* Grid[x+y*MAXCOLS].neighbors=0; will contain nonsense */
  419.                 Grid[x+y*MAXCOLS].color=(-2);
  420.             } else {
  421.                 Grid[x+y*MAXCOLS].neighbors=0;
  422.                 Grid[x+y*MAXCOLS].color=(-1);
  423.             }
  424.     }
  425.     }
  426.  
  427.     /* do some seeding */
  428.     if ((ncols-2)>128) {xr=16+1; xo=1+random()%((ncols-3)-128+1); xa=1;}
  429.     else {xr=((ncols-3)/8)+1; xo=1; xa=((ncols-3)%8)+1;}
  430.     if ((nrows-2)>128) {yr=16+1; yo=1+random()%((nrows-3)-128+1); ya=1;}
  431.     else {yr=((nrows-3)/8)+1; yo=1; ya=((nrows-3)%8)+1;}
  432.     for (count=MAX(xr*yr*4,100); count; count--) {
  433.         /* add up 8 rolls of dice for each coordinate (near gaussian dist) */
  434.         for (i=0, x=xo+random()%xa; i<8; i++) x+=random()%xr;
  435.         for (i=0, y=yo+random()%ya; i<8; i++) y+=random()%yr;
  436.         if (Grid[x+y*MAXCOLS].color==(-1)) {
  437.             Grid[x+y*MAXCOLS].color=0;
  438.             Grid[x+y*MAXCOLS].next = ifirst;
  439.             ifirst=x+y*MAXCOLS;
  440.         }
  441.         if (Grid[x+y*MAXCOLS].color<COLORS-1) Grid[x+y*MAXCOLS].color++;
  442.     }
  443.     
  444.     /* init stasis array */
  445.     for (x=0; x<STATSIZE; x++) stasis[x] = x;
  446.     strack = 0;
  447.     sindex = 0;
  448.     spass = 0;
  449.     countDown = ITERATIONS;
  450.  
  451.     return self;
  452. }
  453.  
  454. /* check for stasis (any period of repetition up to STATSIZE) */
  455. - checkStasis:(int)checksum
  456. {
  457.     int i;
  458.     if (!strack || stasis[(sindex+STATSIZE-strack)%STATSIZE]!=checksum) {
  459.         spass=0;
  460.         strack=0;
  461.         for (i=0; i<STATSIZE; i+=STATIVAL) {
  462.             if (stasis[i]==checksum) {
  463.                 if (i==sindex) strack=STATSIZE;
  464.                 else strack=(sindex+STATSIZE-i)%STATSIZE;
  465.                 break;
  466.             }
  467.         }
  468.     } else {
  469.         spass++;
  470.         if (spass>=STATIVAL) countDown=0; /* must match STATIVAL generations */
  471.                                           /* STATIVAL should be more than 2 */
  472.     }
  473.  
  474.     stasis[sindex++] = checksum;
  475.     if (sindex>=STATSIZE) sindex = 0;
  476.     return self;
  477. }
  478.  
  479. /* given old-cell color, young-cell color, and medium-cell color, */
  480. /* linearly interpolate the whole color table in HSB space */
  481. - computeColors
  482. {
  483.     float yhue, ysat, ybri;
  484.     float mhue, msat, mbri;
  485.     float ohue, osat, obri;
  486.     float chue;
  487.     int i;
  488.  
  489.     NXConvertColorToHSB(youngColor, &yhue, &ysat, &ybri);
  490.     NXConvertColorToHSB(mediumColor, &mhue, &msat, &mbri);
  491.     NXConvertColorToHSB(oldColor, &ohue, &osat, &obri);
  492.  
  493.     /* hue space is circular, so decide which direction to go */
  494.     /* (take the shortest path out of the two possible) */
  495.     if (yhue>mhue && yhue-mhue>0.5) yhue -= 1.0;
  496.     else if (mhue>yhue && mhue-yhue>0.5) yhue += 1.0;
  497.     if (ohue>mhue && ohue-mhue>0.5) ohue -= 1.0;
  498.     else if (mhue>ohue && mhue-ohue>0.5) ohue += 1.0;
  499.  
  500.     colorTable[0]=NXConvertGrayToColor(NX_BLACK);
  501.     /* interpolate from young to medium */
  502.     for (i=1; i<COLORS/3; i++) {
  503.         chue=(float)(yhue*(COLORS/3-i)+mhue*(i-1))/(COLORS/3-1);
  504.         if (chue<0.0) chue += 1.0;
  505.         else if (chue>1.0) chue -= 1.0;
  506.         colorTable[i]=NXConvertHSBToColor(chue,
  507.             (float)(ysat*(COLORS/3-i)+msat*(i-1))/(COLORS/3-1),
  508.             (float)(ybri*(COLORS/3-i)+mbri*(i-1))/(COLORS/3-1));
  509.     }
  510.     /* from medium to old */
  511.     for (i=COLORS/3; i<COLORS; i++) {
  512.         chue=(float)(mhue*(COLORS-1-i)+ohue*(i-COLORS/3))/(COLORS-1-COLORS/3);
  513.         if (chue<0.0) chue += 1.0;
  514.         else if (chue>1.0) chue -= 1.0;
  515.         colorTable[i]=NXConvertHSBToColor(chue,
  516.             (float)(msat*(COLORS-1-i)+osat*(i-COLORS/3))/(COLORS-1-COLORS/3),
  517.             (float)(mbri*(COLORS-1-i)+obri*(i-COLORS/3))/(COLORS-1-COLORS/3));
  518.     }
  519.  
  520.     /* pass colors on to panel also, if needed */
  521.     if (panelLifeView && sharedInspectorPanel) {
  522.         [panelLifeView setYoungColor:youngColor
  523.                        MediumColor:mediumColor
  524.                        OldColor:oldColor];
  525.     }
  526.  
  527.     return self;
  528. }
  529.  
  530. /* quick update called when color has been changed */
  531. - updateViews
  532. {
  533.     /* update the panel view, if needed */
  534.     if (sharedInspectorPanel) [sharedInspectorPanel display];
  535.  
  536.     /* update myself */
  537.     [self lockFocus];
  538.     [self drawSquares];
  539.     [self unlockFocus];
  540.     return self;
  541. }
  542.  
  543. - inspector:sender
  544. {
  545.     char buf[MAXPATHLEN];
  546.     if (!sharedInspectorPanel) {
  547.         sprintf(buf,"%s/SparseLife.nib",[sender moduleDirectory:"SparseLife"]);
  548.         [NXApp loadNibFile:buf owner:self withNames:NO];
  549.         /* initialize some of the panel objects... */
  550.         if (panelYoungColorWell) [panelYoungColorWell setColor:youngColor];
  551.         if (panelMediumColorWell) [panelMediumColorWell setColor:mediumColor];
  552.         if (panelOldColorWell) [panelOldColorWell setColor:oldColor];
  553.         if (panelSizeMatrix) {
  554.             [panelSizeMatrix setEmptySelectionEnabled:YES];
  555.             if (![panelSizeMatrix selectCellWithTag:cellSize]) {
  556.                 [panelSizeMatrix selectCellAt:-1:-1];
  557.             } else {
  558.                 [panelSizeMatrix setEmptySelectionEnabled:NO];
  559.             }
  560.         }
  561.         if (panelStepButton) {
  562.             [panelStepButton sendActionOn:NX_MOUSEDOWNMASK];
  563.             [panelStepButton setContinuous:YES];
  564.             [panelStepButton setPeriodicDelay:PANELTIME/1000.0
  565.                              andInterval:PANELTIME/1000.0];
  566.         }
  567.         [self computeColors]; /* updates color table inside panelLifeView */
  568.     }
  569.     return sharedInspectorPanel;
  570. }
  571.  
  572. - inspectorInstalled
  573. {
  574.     int i;
  575.     [self hideCredits:self];
  576.     if (sharedInspectorPanel) [sharedInspectorPanel display];
  577.     if (panelLifeView && sharedInspectorPanel) {
  578.         installedCountDown=countDown;
  579.         for (i=1; i<=5; i++) {
  580.             [self perform:@selector(doSingleStep:) with:self
  581.                   afterDelay:PANELTIME*i cancelPrevious:(i==1)];
  582.         }
  583.     }
  584.     return self;
  585. }
  586.  
  587. - takeYoungColorFrom:sender
  588. {
  589.     char str[80];
  590.     youngColor=[(NXColorWell *)sender color];
  591.     [self computeColors];
  592.     [self updateViews];
  593.     ColorToString(youngColor,str);
  594.     NXWriteDefault("BackSpace","SparseLifeYoungColor",str);
  595.     return self;
  596. }
  597.  
  598. - takeMediumColorFrom:sender
  599. {
  600.     char str[80];
  601.     mediumColor=[(NXColorWell *)sender color];
  602.     [self computeColors];
  603.     [self updateViews];
  604.     ColorToString(mediumColor,str);
  605.     NXWriteDefault("BackSpace","SparseLifeMediumColor",str);
  606.     return self;
  607. }
  608.  
  609. - takeOldColorFrom:sender
  610. {
  611.     char str[80];
  612.     oldColor=[(NXColorWell *)sender color];
  613.     [self computeColors];
  614.     [self updateViews];
  615.     ColorToString(oldColor,str);
  616.     NXWriteDefault("BackSpace","SparseLifeOldColor",str);
  617.     return self;
  618. }
  619.  
  620. - doSizeMatrix:sender
  621. {
  622.     char str[80];
  623.     int newSize;
  624.     id selected;
  625.     selected=[sender selectedCell];
  626.     if (selected) newSize=[selected tag];
  627.     if (!selected || cellSize==newSize ||
  628.         newSize<MINCELLSIZE || newSize>MAXCELLSIZE) {
  629.         if (![panelSizeMatrix selectCellWithTag:cellSize]) {
  630.             [panelSizeMatrix selectCellAt:-1:-1];
  631.         }
  632.         return self;
  633.     }
  634.     if ([sender isEmptySelectionEnabled]) [sender setEmptySelectionEnabled:NO];
  635.     cellSize=newSize;
  636.     sprintf(str,"%d",cellSize);
  637.     NXWriteDefault("BackSpace","SparseLifeCellSize",str);
  638.     [self setupSquareBuffer];
  639.     [self initLife];
  640.     [self display];
  641.     if (panelLifeView) {
  642.         [panelLifeView setLifeCellSize:cellSize];
  643.         [panelLifeView initLife];
  644.     }
  645.     if (sharedInspectorPanel) [sharedInspectorPanel display];
  646.     return self;
  647. }
  648.  
  649. - doSingleStep:sender
  650. {
  651.     /* don't animate panel if animating another window */
  652.     if (sender==self && installedCountDown!=countDown) return self;
  653.  
  654.     /* should not do the panel animation if the view is not loaded or */
  655.     /* if it has been removed from the inspector window */
  656.     if (panelLifeView && sharedInspectorPanel &&
  657.         [panelLifeView canDraw] && [sharedInspectorPanel canDraw]) {
  658.         [panelLifeView lockFocus];
  659.         [panelLifeView oneStep];
  660.         [panelLifeView unlockFocus];
  661.         [sharedInspectorPanel display];
  662.     }
  663.     return self;
  664. }
  665.  
  666. - doRestart:sender
  667. {
  668.     if (panelLifeView) [panelLifeView initLife];
  669.     if (sharedInspectorPanel) [sharedInspectorPanel display];
  670.     return self;
  671. }
  672.  
  673.  
  674. - showCredits:sender
  675. {
  676.     if (panelCreditsView && sharedInspectorPanel) {
  677.         if ([panelCreditsView isDescendantOf:sharedInspectorPanel]) {
  678.             [panelCreditsView removeFromSuperview];
  679.         }
  680.         [sharedInspectorPanel addSubview:panelCreditsView];
  681.         [sharedInspectorPanel display];
  682.     }
  683.     return self;
  684. }
  685.  
  686. - hideCredits:sender
  687. {
  688.     if (panelCreditsView && sharedInspectorPanel) {
  689.         if ([panelCreditsView isDescendantOf:sharedInspectorPanel]) {
  690.             [panelCreditsView removeFromSuperview];
  691.         }
  692.         [sharedInspectorPanel display];
  693.     }
  694.     return self;
  695. }
  696.  
  697. /* these methods are needed because we should not rearrange the view */
  698. /* hierarchy while a button is active; we queue the rearranging to be */
  699. /* done later as an event, when nothing is locked on the view */
  700.  
  701. - doShowCredits:sender
  702. {
  703.     [self perform:@selector(showCredits:)
  704.           with:self afterDelay:0 cancelPrevious:YES];
  705.     return self;
  706. }
  707.  
  708. - doHideCredits:sender
  709. {
  710.     [self perform:@selector(hideCredits:)
  711.           with:self afterDelay:0 cancelPrevious:YES];
  712.     return self;
  713. }
  714.  
  715. @end
  716.  
  717.  
  718.